#include "StdAfx.h"
#include "SpecifiedChunk.h"
#include "ChunkSpace.h"
#include "ChunkIdentifier.h"
#include "ChunkFileSerializer.h"

#include "ChunkBoundary.h"

#include "ChunkManager.h"

#include "TerrainBlockChunkItem.h"

#include "3rd_part\xml\TiXmlUtility.h"

using namespace ChunkSystem;
using namespace Common;

InternalChunk::InternalChunk( const String& strChunkId, Chunk* pOwnerChunk )
	:Chunk(strChunkId, pOwnerChunk, pOwnerChunk->GetOwnerSpace())
{
}


InternalChunk::~InternalChunk(void)
{
}

bool InternalChunk::_LoadBoundaryInfo( TiXmlNode* pChunkDataSection , BoundariesData& rBoundriesData )
{
	//read boundaries
	//extract boundary info from visual
	TiXmlHandle ptrChunkDataSection(pChunkDataSection);
	TiXmlHandle ptrHullSection = ptrChunkDataSection.FirstChild("Hull");
	assert( ptrHullSection.ToElement() != NULL );
	for( int iChildIdx = 0; ; ++iChildIdx )
	{
		TiXmlHandle ptrBoundary = ptrHullSection.Child("boundary",iChildIdx);
		if( ptrBoundary.Node() == NULL )
			break;

		rBoundriesData.push_back( ptrBoundary.Node() );
	}

	return true;
}

bool InternalChunk::Load( TiXmlElement* pChunkData )
{
	TiXmlHandle ptrChunkData(pChunkData);
	
	//update AABB
	TiXmlHandle aabb = ptrChunkData.FirstChild("boundingBox");
	assert( aabb.ToElement() != NULL );
	m_aabb = TiXmlUtility::ReadAABB( aabb );

	//update Transformation
	//Todo: the transformation info is generated in world editor. and base to a external chunk
	//TiXmlHandle transform = ptrChunkData.FirstChild("Transformation");

	Matrix4 matWorldParent = m_pOwner->GetWorldTM();
	m_aabb.transform( matWorldParent );

	//m_aabb.scale( Vector3( 0.1f, 0.1f, 0.1f ) );

	return Chunk::Load( pChunkData );
};

bool InternalChunk::_FormPortal( Chunk* pToChunk, Portal& oportal )
{
	if( pToChunk == NULL )
		return false;

	for( Boundaries::iterator bit = m_Joints.begin();
		bit != m_Joints.end();
		bit++)
	{
		ChunkBoundary::Portals unBoundPortals = (*bit)->GetUnboundPortals();
		// go through all their unbound portals
		for ( ChunkBoundary::Portals::iterator pit = unBoundPortals.begin(); pit != unBoundPortals.end(); pit++)
		{
			if ( !_CanBind( oportal, **pit, pToChunk, this ) )
				continue;

			(*pit)->BindChunk( pToChunk );

			// ok that's it. we leave it unbound for now as
			// it will soon be bound by an ordinary 'bind' call.
			return true;

			// we could recalculate centres, but we may as well use
			//  the existing cached ones
		}
	}

	return false;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////
String InternalChunkFactory::GetType(void)
{
	return ChunkFileSerializer::GetTag( ChunkFileSerializer::eChunk );
}

ChunkItem* InternalChunkFactory::Create( ChunkItem* pOwner, TiXmlElement* pChunkItemData )
{
	//put this chunk into the loading queue
	Chunk* pOwnerChunk = static_cast< Chunk* >( pOwner );
	assert( pOwnerChunk != NULL );
	assert( pChunkItemData->FirstChild() != NULL );

	std::string strInternalChunkName = pChunkItemData->FirstChild()->ToText()->Value();
	int iToken = strInternalChunkName.find(".chunk");
	if( iToken != -1 )
		strInternalChunkName = strInternalChunkName.substr( 0, iToken );

	Chunk* pNewInternalChunk = new InternalChunk( strInternalChunkName, pOwnerChunk );
	pNewInternalChunk->GetOwnerSpace()->AddIdenticalChunk( pNewInternalChunk );

	ChunkManager::GetInstance().LoadChunk( pNewInternalChunk );


	return NULL;
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////////////ExternalChunk
ExternalChunk::ExternalChunk( const String& rId, ChunkSpace* pOwnerSpace )
	:Chunk( rId, NULL, pOwnerSpace )
{
	GridCoord pos = ChunkIdentifier::ExternalChunkPathToGridCoord( m_strId );
	Vector3 worldPos = m_pOwnerSpace->GridCoordToPos(pos);
	m_matWorldTM.setTrans(worldPos);

	float fResolution = m_pOwnerSpace->GetGridResolution();
	m_aabb.setExtents( Vector3(worldPos.x,m_fMinHeight,worldPos.z) ,Vector3(worldPos.x + fResolution,m_fMaxHeight, worldPos.z + fResolution));
}

ExternalChunk::~ExternalChunk()
{
}

TiXmlNode* ExternalChunk::_CreateBoundaryData( TiXmlNode* pChunkRoot, const Vector3& normal , float d)
{
	TiXmlNode* newBoundary = pChunkRoot->InsertEndChild( TiXmlElement("boundary") );
	Plane rBoundary( normal, d );
	TiXmlUtility::WritePlane( rBoundary, newBoundary );
	
	return newBoundary;
}

//for automatically import terrain test
bool ExternalChunk::Load( TiXmlElement* pChunkData )
{
	//if not, create one by .ter source file.
	//todo: this block of logic is just for testing
	TiXmlHandle ptrChunkData( pChunkData );
	if( ptrChunkData.FirstChild("terrain").ToElement() == NULL )
	{
		GridCoord gridCoord = m_pOwnerSpace->PosToGridCoord( m_matWorldTM.getTrans() );

		TiXmlNode* pTerrainNode = pChunkData->InsertEndChild( TiXmlElement("terrain") );
		assert( pTerrainNode != NULL );
		TiXmlNode* pResourceNode = pTerrainNode->InsertEndChild( TiXmlElement("resource") );
		assert( pResourceNode != NULL );

		std::stringstream ss;
		ss << "terrain_chunk_data/" << "_x" << gridCoord.first << "_y" << gridCoord.second << ".ter" << std::endl;
		pResourceNode->InsertEndChild( TiXmlText( ss.str() ) );
		ss.clear();
	}

	return Chunk::Load( pChunkData );
}

TiXmlNode* ExternalChunk::_CreatePortalData( TiXmlNode* pBoundary, const String& portalLabel, const String& strConnectChunk, const Vector3& uAxis, 
	const Vector3& pt0 , const Vector3& pt1 , const Vector3& pt2 , const Vector3& pt3 )
{
	TiXmlNode* newPortal = pBoundary->InsertEndChild( TiXmlElement("portal") );

	TiXmlNode* pLabel = newPortal->InsertEndChild( TiXmlElement("label" ) );
	pLabel->InsertEndChild( TiXmlText(portalLabel) );

	TiXmlNode* pToChunk = newPortal->InsertEndChild( TiXmlElement("chunk" ) );
	pToChunk->InsertEndChild( TiXmlText(strConnectChunk) );
	
	TiXmlNode* pToAxis = newPortal->InsertEndChild( TiXmlElement("uAxis" ) );
	pToAxis->InsertEndChild(TiXmlText(TiXmlUtility::Vector3ToString(uAxis)));

	TiXmlNode* pPoint = newPortal->InsertEndChild( TiXmlElement("point") );
	pPoint->InsertEndChild(TiXmlText (TiXmlUtility::Vector3ToString(pt0)));

	pPoint = newPortal->InsertEndChild( TiXmlElement("point") );
	pPoint->InsertEndChild(TiXmlText (TiXmlUtility::Vector3ToString(pt1) ) );

	pPoint = newPortal->InsertEndChild( TiXmlElement("point") );
	pPoint->InsertEndChild(TiXmlText ( TiXmlUtility::Vector3ToString(pt2) ) );

	pPoint = newPortal->InsertEndChild( TiXmlElement("point") );
	pPoint->InsertEndChild(TiXmlText( TiXmlUtility::Vector3ToString(pt3) ) );

	return newPortal;
}

bool ExternalChunk::_LoadBoundaryInfo( TiXmlNode* pData , BoundariesData& rBoundriesData)
{
	TiXmlHandle ptrChunk( pData );

	GridCoord pos = ChunkIdentifier::ExternalChunkPathToGridCoord( m_strId );

	if( m_pOwnerSpace == NULL )
		return false;

	float fResolution = m_pOwnerSpace->GetGridResolution();

	TiXmlNode* pBoundary = NULL;

	for (int i = 0; i < 6; ++i)
	{
		if( i == 0 )
		{
			// right
			GridCoord rightPortal ( pos.first + 1 , pos.second );
			pBoundary = _CreateBoundaryData( pData, Vector3( 1.f, 0.f, 0.f ), 0.f );
			_CreatePortalData( pBoundary, "right" , ChunkIdentifier::GridCoordToExternalChunkPath( rightPortal ) , Vector3( 0.f, 1.f, 0.f ),
				Vector3( m_fMinHeight, 0.f, 0.f ),
				Vector3( m_fMaxHeight, 0.f, 0.f ),
				Vector3( m_fMaxHeight, fResolution, 0.f ),
				Vector3( m_fMinHeight, fResolution, 0.f ) );
		}
		else if (i == 1)
		{
			// left
			GridCoord leftPortal ( pos.first - 1 , pos.second );
			pBoundary = _CreateBoundaryData( pData, Vector3( -1.f, 0.f, 0.f ), -fResolution );
			_CreatePortalData( pBoundary, "left", ChunkIdentifier::GridCoordToExternalChunkPath( leftPortal ), Vector3( 0.f, 0.f, 1.f ),
				Vector3( 0.f, m_fMinHeight, 0.f ),
				Vector3( fResolution, m_fMinHeight, 0.f ),
				Vector3( fResolution, m_fMaxHeight, 0.f ),
				Vector3( 0.f, m_fMaxHeight, 0.f ) );

		}
		else if (i == 2)
		{
			// bottom
			pBoundary = _CreateBoundaryData( pData, Vector3( 0.f, 1.f, 0.f ), m_fMinHeight );
			_CreatePortalData( pBoundary, "bottom", "earth", Vector3( 0.f, 0.f, 1.f ),
				Vector3( 0.f, 0.f, 0.f ),
				Vector3( fResolution, 0.f, 0.f ),
				Vector3( fResolution, fResolution, 0.f ),
				Vector3( 0.f, fResolution, 0.f ) );
		}
		else if (i == 3)
		{
			// top
			pBoundary = _CreateBoundaryData( pData, Vector3( 0.f, -1.f, 0.f ), -m_fMaxHeight );
			_CreatePortalData( pBoundary, "top", "heaven", Vector3( 1.f, 0.f, 0.f ),
				Vector3( 0.f, 0.f, 0.f ),
				Vector3( fResolution, 0.f, 0.f ),
				Vector3( fResolution, fResolution, 0.f ),
				Vector3( 0.f, fResolution, 0.f ) );
		}
		else if (i == 4)
		{
			// back
			pBoundary = _CreateBoundaryData( pData, Vector3( 0.f, 0.f, 1.f ), 0.f );
			GridCoord backPortal ( pos.first , pos.second + 1 );
			_CreatePortalData( pBoundary, "back",ChunkIdentifier::GridCoordToExternalChunkPath( backPortal ), Vector3( 1.f, 0.f, 0.f ),
				Vector3( 0.f, m_fMinHeight, 0.f ),
				Vector3( fResolution, m_fMinHeight, 0.f ),
				Vector3( fResolution, m_fMaxHeight, 0.f ),
				Vector3( 0.f, m_fMaxHeight, 0.f ) );
		}
		else if (i == 5)
		{
			// front
			pBoundary = _CreateBoundaryData( pData, Vector3( 0.f, 0.f, -1.f ), -fResolution );
			GridCoord frontPortal ( pos.first , pos.second - 1 );

			_CreatePortalData( pBoundary, "front",ChunkIdentifier::GridCoordToExternalChunkPath( frontPortal ), Vector3( 0.f, 1.f, 0.f ),
				Vector3( m_fMinHeight, 0.f, 0.f ),
				Vector3( m_fMaxHeight, 0.f, 0.f ),
				Vector3( m_fMaxHeight, fResolution, 0.f ),
				Vector3( m_fMinHeight, fResolution, 0.f ) );
		}
		rBoundriesData.push_back(pBoundary);
	}

	return true;
}

bool ExternalChunk::_FormPortal( Chunk* pToChunk, Portal& oportal )
{
	if( pToChunk == NULL )
		return false;

	Portal::PortalType eOutPortalType = oportal.GetPortalType();
	if( eOutPortalType != Portal::eInvasive )
		return false;

	for( Boundaries::iterator bit = m_Joints.begin();
		bit != m_Joints.end();
		bit++)
	{
		ChunkBoundary::Portals unBoundPortals = (*bit)->GetUnboundPortals();
		// go through all their unbound portals
		for ( ChunkBoundary::Portals::iterator pit = unBoundPortals.begin(); pit != unBoundPortals.end(); pit++)
		{
			if ( !_CanBind( oportal, **pit, pToChunk, this ) )
				continue;

			(*pit)->BindChunk( pToChunk );

			// ok that's it. we leave it unbound for now as
			// it will soon be bound by an ordinary 'bind' call.
			return true;

			// we could recalculate centres, but we may as well use
			//  the existing cached ones
		}
	}

	//generate for the outPortal
	
	/*
	// we'd better form that portal then
	ChunkBoundary* pOutBoundary = oportal.GetOwnerBoundary();
	assert( pOutBoundary != NULL );
	
	const Plane& rPlane = pOutBoundary->GetPlane();
	Vector3 norWorld = pToChunk->GetWorldTM() * rPlane.normal * -1.f;
	Vector3 centreWorld = oportal.centre;				// facing other way
//	PlaneEq wplane( wcentre, wnormal );
	Vector3 lnormal = transformInverse_.applyVector( wnormal );
	Vector3 lcentre = transformInverse_.applyPoint( wcentre );
	PlaneEq lplane( lcentre, lnormal );

	// see if any existing planes fit
	bool isInternal = false;
	ChunkBoundaries::iterator bit = joints_.end();

	// ok, make a new one then
	if (bit == joints_.end())
	{
		isInternal = true;

		ChunkBoundary * pCB = new ChunkBoundary( NULL, pMapping_ );
		pCB->plane_ = lplane;
		joints_.push_back( pCB );
		bit = joints_.end() - 1;
	}

	// make up the portal on it
	ChunkBoundary::Portal * portal =
		new ChunkBoundary::Portal( NULL, (*bit)->plane_, pMapping_ );
	portal->internal = isInternal;
	portal->pChunk = pChunk;

	// Figure out the basis for the polygon in this chunk's local space

	// 1) Find the cartesian axis that is most perpendicular to the lnormal
	// vector.
	// 1.a) Take the dot product of the lnormal vector with each axis
	float NdotX = lnormal.dotProduct( Vector3(1.0f, 0.0f, 0.0f) );
	float NdotY = lnormal.dotProduct( Vector3(0.0f, 1.0f, 0.0f) );
	float NdotZ = lnormal.dotProduct( Vector3(0.0f, 0.0f, 1.0f) );

	// 1.b) The value which is closest to zero represents the cartesian
	// axis that is the most perpendicular to the lnormal vector
	Vector3 cartesianAxis;

	// First test X against Y
	if ( fabsf(NdotX) < fabsf(NdotY) )
		// If here, test X against Z
		if ( fabsf(NdotX) < fabsf(NdotZ) )
			// X most perpendicular
			cartesianAxis = Vector3(1.0f, 0.0f, 0.0f);
		else
			// Z most perpendicular
			cartesianAxis = Vector3(0.0f, 0.0f, 1.0f);
	else
		// If here, test Y against Z
		if ( fabsf(NdotY) < fabsf(NdotZ) )
			// Y most perpendicular
			cartesianAxis = Vector3(0.0f, 1.0f, 0.0f);
		else
			// Z most perpendicular
			cartesianAxis = Vector3(0.0f, 0.0f, 1.0f);

	// 2) Now that the most perpendicular axis has been found, it can
	// be used to find the tangent vector, luAxis
	Vector3 luAxis = lnormal.crossProduct( cartesianAxis );

	// 3) The normal and the tangent vectors can now be used to find the
	// binormal (remember cartesianAxis was only the closest perpendicular
	// axis, it probably isn't going to be perpendicular)
	Vector3 lvAxis = lnormal.crossProduct( luAxis );

	// turn it into a matrix (actually using matrix for ordinary maths!)
	Matrix basis = Matrix::identity;
	memcpy( basis[0], luAxis, sizeof(Vector3) );
	memcpy( basis[1], lvAxis, sizeof(Vector3) );
	memcpy( basis[2], lnormal, sizeof(Vector3) );
		// error from plane is in the z.
	basis.translation( lnormal * lplane.d() / lnormal.lengthSquared() );
	Matrix invBasis; invBasis.invert( basis );

	// use it to convert the world coordinates of the points into local space
	for (uint i = 0; i < oportal.points.size(); i++)
	{
		// point starts in form portal's space
		Vector3 fpt =
			oportal.uAxis * oportal.points[i][0] +
			oportal.vAxis * oportal.points[i][1] +
			oportal.origin;
		// now in form chunk's space
		Vector3 wpt = pChunk->transform_.applyPoint( fpt );
		// now in world space
		Vector3 lpt = transformInverse_.applyPoint( wpt );
		// now in our chunk's space
		Vector3 ppt = invBasis.applyPoint( lpt );
		// and finally in our portal's space
		portal->points.push_back( Vector2( ppt.x, ppt.y ) );
	}
	portal->uAxis = basis.applyToUnitAxisVector(0); //luAxis;
	portal->vAxis = basis.applyToUnitAxisVector(1); //lvAxis;
	portal->origin = basis.applyToOrigin();
	portal->lcentre = transformInverse_.applyPoint( wcentre );
	portal->centre = wcentre;

	// now do the dodgy reverse portal hack thing, from ChunkBoundary::Portal
	// hack for 4-sided polygons - reverse order of two middle points
	// if plane from first three points points the wrong way.
	if (portal->points.size() == 4)
	{
		PlaneEq testPlane(
			portal->points[0][0] * portal->uAxis + portal->points[0][1] * portal->vAxis + portal->origin,
			portal->points[1][0] * portal->uAxis + portal->points[1][1] * portal->vAxis + portal->origin,
			portal->points[2][0] * portal->uAxis + portal->points[2][1] * portal->vAxis + portal->origin );
		Vector3 n1 = (*bit)->plane_.normal();
		Vector3 n2 = testPlane.normal();
		n1.normalise();	n2.normalise();
		if ((n1 + n2).length() < 1.f)	// should be 2 if equal
		{
			Vector2 tpt = portal->points[1];
			portal->points[1] = portal->points[3];
			portal->points[3] = tpt;
		}
	}

	// and add it as an unbound portal
	(*bit)->addInvasivePortal( portal );

	// let the caches know things have changed
	this->notifyCachesOfBind( false );

	// and record if we now have internal chunks
	hasInternalChunks_ |= isInternal;

	*/

	return false;
}

//////////////////////////////
HeavenChunk::HeavenChunk(ChunkSpace* pOwnerSpace)
	:Chunk("heaven", NULL, pOwnerSpace)
{
}

//////////////////////////////
HellChunk::HellChunk(ChunkSpace* pOwnerSpace)
	:Chunk("hell", NULL, pOwnerSpace)
{
}

